/*
 * Copyright 2013 Agustina Martinez-Garcia

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
package uk.ljmu.mulgaraifz.httprequests;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;
import org.dom4j.Element;
import org.dom4j.Node;
import org.igfay.jfig.JFig;
import org.igfay.jfig.JFigException;

import uk.ljmu.mulgaraifz.errorhandling.MulgaraException;
import uk.ljmu.mulgaraifz.model.CollectionElement;
import uk.ljmu.mulgaraifz.sparql.QueryConstants;
import uk.ljmu.mulgaraifz.utilities.Constants;
import uk.ljmu.mulgaraifz.utilities.XmlTraverse;

public class GenerateTree {
    private static Logger log = Logger.getLogger(GenerateTree.class);

    private static String GET_COLLECTIONS_ONE_QUERY;
    private String firstId = "";
    private static String SCRIPT_OPENING;
    private static String SCRIPT_CLOSING;
    private Map<String, Integer> mapIdentifiers = new HashMap<String, Integer>();
    private int counter = 1;
    private MulgaraOperations mulgObject;
    
    private List<CollectionElement> collectionsTree;
    static {
	StringBuilder sb = new StringBuilder();
	// AMG TO GENERATE COLLECTIONS/SUB-COLLECTIONS TREE QUERY 
	sb.append("PREFIX dc: <http://purl.org/dc/elements/1.1/> ");
	sb.append("PREFIX rel: <info:fedora/fedora-system:def/relations-external#> "); 
	sb.append("PREFIX dcmitype: <http://purl.org/dc/dcmitype/> ");
	sb.append("PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> "); 
	sb.append("SELECT distinct ?s ?col ?child ?title ");
	sb.append("FROM $GRAPH$ ");
	sb.append("{ ");
	sb.append("?s <dc:title> ?title. ");
	sb.append("{");
	sb.append("?s <rdf:type> <dcmitype:Collection>. ");
	sb.append("optional { ?s <dc:identifier> ?id. ");
	sb.append("optional { ?s <rel:isPartOf> ?col. }");
	sb.append("optional { ?child <rdf:type> <dcmitype:Collection>. optional {?child <rel:isPartOf> ?s.}}} ");
	sb.append("} UNION ");
	sb.append("{ ");
	sb.append("?s <rdf:type> <dcmitype:Collection>. ");
	sb.append("optional { ?s <dc:identifier> ?id. ");
	sb.append("optional { ?s <rel:isMemberOf> ?col. }");
	sb.append("optional { ?child <rdf:type> <dcmitype:Collection>. optional {?child <rel:isMemberOf> ?s.}}} ");
	sb.append("} UNION ");
	sb.append("{ ");
	sb.append("?s <rdf:type> <dcmitype:Collection>. ");
	sb.append("optional { ?s <dc:identifier> ?id. ");
	sb.append("optional { ?s <rel:isMemberOfCollection> ?col. }");
	sb.append("optional { ?child <rdf:type> <dcmitype:Collection>. optional {?child <rel:isMemberOfCollection> ?s.}}} ");
	sb.append("} "); 
	sb.append("}"); 
	GET_COLLECTIONS_ONE_QUERY = sb.toString();
	sb.delete(0, sb.toString().length());
	sb.append("<script type=\"text/javascript\">\n");
	sb.append("<!--\n");
	sb.append("d = new dTree('d');\n");
	sb.append("d.add(0,-1,'Collection in spreadsheet');\n");
	SCRIPT_OPENING = sb.toString();
	sb.delete(0, sb.toString().length());
	sb.append("document.write(d);\n");
	sb.append("//-->\n");
	sb.append("</script>\n");
	SCRIPT_CLOSING = sb.toString();
	sb.delete(0, sb.toString().length());
    }
    private StringBuilder htmlSubTree = new StringBuilder();
    /**
     * @param collectionsTree the collectionsTree to set
     */
    public void setCollectionsTree(List<CollectionElement> collectionsTree) {
	this.collectionsTree = collectionsTree;
    }
    /**
     * @return the collectionsTree
     */
    public List<CollectionElement> getCollectionsTree() {
	return collectionsTree;
    }
    /**
     * @param firstId the firstId to set
     */
    public void setFirstId(String firstId) {
	this.firstId = firstId;
    }
    /**
     * @return the firstId
     */
    public String getFirstId() {
	return firstId;
    }
    /**
     * @param htmlTree the htmlTree to set
     */
    public void setHtmlSubTree(StringBuilder htmlTree) {
	this.htmlSubTree = htmlTree;
    }
    /**
     * @return the htmlTree
     */
    public StringBuilder getHtmlSubTree() {
	return htmlSubTree;
    }
    
    public GenerateTree() {
	try {
	    this.mulgObject = new MulgaraOperationsImpl();
	} catch (MulgaraException e) {
	    log.error("Error ", e);
	}
    }
    public GenerateTree(MulgaraOperations mulgaraObject) {
	 this.mulgObject = mulgaraObject;
    }
    //FUNCTIONS
    public void printCollectionsTree() {
	List<String> rootCollections;
	this.htmlSubTree.delete(0, this.htmlSubTree.toString().length());
	try {
	    this.firstId = "";
	    rootCollections = this.mulgObject.getRootCollections();
	    if (rootCollections.size() == 0) {
		return;
	    }
	    this.collectionsTree = this.constructCollectionsTree();
	    int depth = 0;
	    this.htmlSubTree.append(SCRIPT_OPENING);
	    for (String root : rootCollections) {
		if (firstId == "")
		    this.firstId = root;
		CollectionElement elem = CollectionElement.findElement(root.replace(Constants.INFO_FEDORA_PREFIX, ""), this.collectionsTree);
		if (elem != null)
		    this.traverseTree(elem, depth, "0");
	    }
	    this.htmlSubTree.append(SCRIPT_CLOSING);
	    //Clean Objects
	    this.collectionsTree.clear();
	    this.mapIdentifiers.clear();
	    this.counter = 1;
	} catch (MulgaraException e) {
	    log.error("Error ", e);
	}
    }
    public List<CollectionElement> constructCollectionsTree() {
	List<CollectionElement> treeList = new ArrayList<CollectionElement>();
	boolean add = false;
	// Query Mulgara to obtain collections list
	Iterator<?> it = this.retrieveCollectionsOneQuery();
	// Parse iterator and construct tree structure
	if (it != null) {
	    try {
		if (it.hasNext())
		    log.debug("ITERATOR HAS ELEMENTS");
		while (it.hasNext()) {
		    String parent = "", child = "", title = "";
		    // Iterate over result elements
		    Node result = (Element) it.next();
		    // FEDORA ID
		    Element idElement = (Element) result.selectNodes("child::*[@name='s']").get(0);
		    Element insideBind = (Element) idElement.elements().get(0);
		    String idData = insideBind.getText().replace(Constants.INFO_FEDORA_PREFIX, "");
		    this.addElementToMap(idData);
		    //id, col and child are the variables bound in the Sparql query
		    Element parentElement = (Element) result
			    .selectSingleNode("child::*[@name='col']");
		    if (parentElement != null) {
			// FEDORA collection parent
			insideBind = (Element) parentElement.elements().get(0);
			parent = insideBind.getText();
		    }
		    Element childElement = (Element) result
			    .selectSingleNode("child::*[@name='child']");
		    if (childElement != null) {
			// FEDORA collection child
			insideBind = (Element) childElement.elements().get(0);
			child = insideBind.getText();
		    }
		    Element titleElement = (Element) result
			    .selectSingleNode("child::*[@name='title']");
		    if (titleElement != null) {
			// FEDORA collection title
			insideBind = (Element) titleElement.elements().get(0);
			title = insideBind.getText();
		    }
		    CollectionElement auxElem = CollectionElement.findElement(
			    idData, treeList);
		    if (auxElem == null) {
			add = true;
			auxElem = new CollectionElement();
			auxElem.setCollectionId(idData);
		    }
		    if (parent == "") {
			auxElem.setRootCollection(true);
		    } else {
			auxElem.setRootCollection(false);
			auxElem.getImmediateParents().add(parent.replace(Constants.INFO_FEDORA_PREFIX, ""));
		    }
		    if (child != "") {
			auxElem.getImmediateChildren().add(child.replace(Constants.INFO_FEDORA_PREFIX, ""));
		    }
		    if (title != "") {
			auxElem.setCollectionTitle(title);
		    } else {
			auxElem
				.setCollectionTitle("Title not available for collection with ID: "
					+ idData);
		    }
		    if (add) {
			add =false;
			treeList.add(auxElem);
			log.debug(auxElem.getCollectionTitle());
		    }
		}		
	    }
	    catch (Exception ex) {
		log.error("Error in constructCollectionsTree: ", ex);
	    }
	} else {
	    log.debug("NO ELEMENTS IN ITERATOR");
	}

	return treeList;	
    }
    
    public Iterator<?> retrieveCollectionsOneQuery() {
	String strBody[];

	try {

	    String query = GET_COLLECTIONS_ONE_QUERY;
	    String constantValue = "";

	    constantValue = JFig.getInstance().getValue("mulgaraServer",
		    "GRAPH");

	    if (!constantValue.startsWith(Constants.URI_BEGIN))
		constantValue = Constants.URI_BEGIN + constantValue
			+ Constants.URI_END;
	    query = QueryConstants.parseQuery(query, "GRAPH", constantValue);

	    String format = mulgObject.getHttpMan().get_OutputFormats().get(
		    "xml");
	    strBody = mulgObject.getHttpMan().httpSparqlQuery(query, format);

	    // Obtain XML document from results
	    Iterator<?> iterator = XmlTraverse
		    .createSparqlXMLIterator(strBody[0]);

	    if (iterator != null && iterator.hasNext()) {
		return iterator;
	    } else {
		log.info("No Qudex Collections in the Repository");
		return null;
	    }
	} catch (MulgaraException e) {
	    log.error("Error in retrieveQudexCollectionsOneQuery: ", e);
	    return null;
	} catch (JFigException e) {
	    log.error("Error in retrieveQudexCollectionsOneQuery: ", e);
	    return null;
	}
    }
    
    private void traverseTree(CollectionElement currentElement, int depth, String imParent) {
	if (!currentElement.isDisplayed()) {
	    currentElement.setDisplayed(true);
	    //d.add(9,0,'My Pictures','example01.html','Pictures I\\'ve taken over the years','','','css/plus.gif');
	    //d.add(9,0,'My Pictures','example01.html','Pictures I\\'ve taken over the years','','','css/leaf.png');
	    
	    // Opening LI
	    int i = 0;
	    if (currentElement.getImmediateChildren().size() > 0) {
		String currentElementChild = currentElement.getImmediateChildren()
			.get(i);
		this.htmlSubTree.append("d.add(");
		this.htmlSubTree.append(this.mapIdentifiers.get(currentElement.getCollectionId()) == null ? "0" : 
		    this.mapIdentifiers.get(currentElement.getCollectionId()));
		this.htmlSubTree.append(", ");
		this.htmlSubTree.append(this.mapIdentifiers.get(imParent) == null ? "0" : 
		    this.mapIdentifiers.get(imParent));
		this.htmlSubTree.append(", '");
		this.htmlSubTree.append((currentElement.getCollectionTitle().length() > 30) ? currentElement.getCollectionTitle().substring(0, 30).replace("'", "\\'") + "..." :
		    currentElement.getCollectionTitle().replace("'", "\\'"));
		this.htmlSubTree.append("', '");
		this.htmlSubTree.append("${flowUrl}&_eventId=updateCollection&selectedCollection=");
		this.htmlSubTree.append(currentElement.getCollectionId());
		this.htmlSubTree.append("', '");
		this.htmlSubTree.append(currentElement.getCollectionTitle().replace("'", "\\'"));
		this.htmlSubTree.append("');\n");
		while (i < currentElement.getImmediateChildren().size()) {
		    String immediateParent = "", rootParent="";
		    CollectionElement elemChild = CollectionElement.findElement(
			    currentElementChild, this.collectionsTree);
		    //Find immParent that is not a root Collection
		    for (String parent : elemChild.getImmediateParents()) {
			CollectionElement elem = CollectionElement.findElement(parent, this.collectionsTree);
			if (elem.isRootCollection()) {
			    rootParent = elem.getCollectionId();
			    continue;
			} else {
			    immediateParent = elem.getCollectionId();
			    break;
			}
		    }
		    if (immediateParent == "") {
			immediateParent = rootParent;
		    }
		    // Recursively traverse the tree structure of the child node
		    traverseTree(CollectionElement.findElement(
			    currentElementChild, this.collectionsTree), depth + 1, immediateParent);
		    i++;
		    if (i < currentElement.getImmediateChildren().size())
			currentElementChild = currentElement
				.getImmediateChildren().get(i);
		}
	    } else {
		this.htmlSubTree.append("d.add(");
		this.htmlSubTree.append(this.mapIdentifiers.get(currentElement.getCollectionId()) == null ? "0" : 
		    this.mapIdentifiers.get(currentElement.getCollectionId()));
		this.htmlSubTree.append(", ");
		this.htmlSubTree.append(this.mapIdentifiers.get(imParent) == null ? "0" : 
		    this.mapIdentifiers.get(imParent));
		this.htmlSubTree.append(", '");
		this.htmlSubTree.append((currentElement.getCollectionTitle().length() > 30) ? currentElement.getCollectionTitle().substring(0, 30).replace("'", "\\'") + "..." :
		    currentElement.getCollectionTitle().replace("'", "\\'"));
		this.htmlSubTree.append("', '");
		this.htmlSubTree.append("${flowUrl}&_eventId=updateCollection&selectedCollection=");
		this.htmlSubTree.append(currentElement.getCollectionId());
		this.htmlSubTree.append("', '");
		this.htmlSubTree.append(currentElement.getCollectionTitle().replace("'", "\\'"));
		this.htmlSubTree.append("','','','leaf.png');\n");
	    }
	}
    }
    
    public void replaceUrl(String flowUrl) {
	String tree = this.htmlSubTree.toString();
	this.htmlSubTree.replace(0, this.htmlSubTree.length(), tree.replace("${flowUrl}", flowUrl));
    }
    private void addElementToMap(String identifier) {
	if (!this.mapIdentifiers.containsKey(identifier)) {
	    this.mapIdentifiers.put(identifier, counter++);
	}
    }
}
